package cn.mehoyo.certificateGeneration.service.impl;

import cn.mehoyo.certificateGeneration.common.LocalCacheMap;
import cn.mehoyo.certificateGeneration.common.util.DnsWaitUtil;
import cn.mehoyo.certificateGeneration.entity.CertificateResult;
import cn.mehoyo.certificateGeneration.entity.DnsChallengeInfo;
import cn.mehoyo.certificateGeneration.enums.ACME;
import cn.mehoyo.certificateGeneration.service.CertificateService;
import lombok.extern.slf4j.Slf4j;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import org.shredzone.acme4j.*;
import org.shredzone.acme4j.challenge.Dns01Challenge;
import org.shredzone.acme4j.exception.AcmeServerException;
import org.shredzone.acme4j.util.CSRBuilder;
import org.shredzone.acme4j.util.KeyPairUtils;


import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.security.KeyPair;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 证书生成实现类
 * @author llh
 */
@Slf4j
@Component
public class LetsEncryptCertificateServiceImpl implements CertificateService {

    static {
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
    }

    private static final LocalCacheMap CACHE_MAP = LocalCacheMap.initialize();

    @Inject("${solon.env}")
    private String env;

    @Override
    public DnsChallengeInfo getDnsChallengeInfo(String domain, ACME acme,String eabKid,String eabHmacKey) throws Exception {
        // 账户密钥
        KeyPair accountKey = KeyPairUtils.createKeyPair(2048);

        Session session = getSession(acme);
        Account account = new AccountBuilder()
                .useKeyPair(accountKey)
                .agreeToTermsOfService()
                .create(session);

        // 创建订单
        Order order = account.newOrder()
                .domain(domain)
                .create();

        Authorization auth = order.getAuthorizations().get(0);
        Dns01Challenge challenge = (Dns01Challenge) auth.findChallenge(Dns01Challenge.TYPE)
                .orElseThrow(() -> new IllegalStateException("未找到 DNS-01 验证类型"));

        String digest = challenge.getDigest();
        String recordName = "_acme-challenge." + auth.getIdentifier().getDomain();

        CACHE_MAP.remove("order:" + domain);
        CACHE_MAP.remove("accountKey:" + domain);
        CACHE_MAP.remove("domainKey:" + domain);
        CACHE_MAP.put("order:" + domain, order);
        CACHE_MAP.put("accountKey:" + domain, accountKey);
        CACHE_MAP.put("domainKey:" + domain, KeyPairUtils.createKeyPair(2048));

        // 返回 DNS 验证信息
        return new DnsChallengeInfo(recordName, digest,domain);
    }


    @Override
    public CertificateResult generateCertificate(String domain) throws Exception {
        Order order = (Order) CACHE_MAP.get("order:" + domain);
        KeyPair accountKey = (KeyPair) CACHE_MAP.get("accountKey:" + domain);
        KeyPair domainKey = (KeyPair) CACHE_MAP.get("domainKey:" + domain);

        if (order == null || accountKey == null) {
            throw new IllegalStateException("订单不存在或已过期");
        }
        List<Authorization> authorizations;
        try{
            authorizations = order.getAuthorizations();
        }catch (Exception e){
            log.error("重新登陆");
            ACME acme = getAcmeProvider(order);
            Session session = getSession(acme);
            order.rebind(new AccountBuilder().useKeyPair(accountKey).createLogin(session));
            authorizations = order.getAuthorizations();
        }
        // 执行 DNS 验证
        for (Authorization auth : authorizations) {
            Dns01Challenge challenge = (Dns01Challenge) auth.findChallenge(Dns01Challenge.TYPE)
                    .orElseThrow(() -> new IllegalStateException("未找到 DNS-01 验证类型"));
            DnsWaitUtil.waitForDnsPropagation(auth.getIdentifier().getDomain(), challenge.getDigest(),"1.1.1.1",60);
            try{
                challenge.trigger();
            }catch (Exception e){
                if(e instanceof AcmeServerException){
                    CACHE_MAP.remove("order:" + domain);
                    CACHE_MAP.remove("accountKey:" + domain);
                    CACHE_MAP.remove("domainKey:" + domain);
                    throw new RuntimeException("DNS 验证失败,请重新获取DNS验证信息",e);
                }
            }


            int retries = 10;
            while (challenge.getStatus() != Status.VALID && retries-- > 0) {
                TimeUnit.SECONDS.sleep(5);
                challenge.update();
            }

            if (challenge.getStatus() != Status.VALID) {
                throw new RuntimeException("DNS 验证失败！");
            }
        }

        // 生成 CSR
        CSRBuilder csrb = new CSRBuilder();
        csrb.addDomain(domain);
        csrb.sign(domainKey);

        // 提交 CSR
        order.execute(csrb.getEncoded());
        while (order.getStatus() != Status.VALID) {
            TimeUnit.SECONDS.sleep(5);
            order.update();
        }

        // 获取证书
        Certificate cert = order.getCertificate();
        if (cert == null) {
            throw new RuntimeException("未能获取到证书");
        }
        Files.createDirectories(Paths.get("cert"));
        Path zipPath = Paths.get("cert", domain + ".zip");

        // 关键点：构造好要写入的文本后再写入 ZipOutputStream，避免在 zos 上创建会被关闭的 Writer
        StringBuilder pemBuilder = new StringBuilder();
        for (X509Certificate c : cert.getCertificateChain()) {
            pemBuilder.append("-----BEGIN CERTIFICATE-----\n");
            pemBuilder.append(Base64.getMimeEncoder(64, new byte[]{'\n'}).encodeToString(c.getEncoded()));
            pemBuilder.append("\n-----END CERTIFICATE-----\n\n");
        }
        byte[] pemBytes = pemBuilder.toString().getBytes(StandardCharsets.UTF_8);

        StringWriter sw = new StringWriter();
        KeyPairUtils.writeKeyPair(domainKey, sw);
        byte[] keyBytes = sw.toString().getBytes(StandardCharsets.UTF_8);

        try (ZipOutputStream zos = new ZipOutputStream(
                Files.newOutputStream(zipPath, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING))) {

            zos.putNextEntry(new ZipEntry(domain + ".pem"));
            zos.write(pemBytes);
            zos.closeEntry();

            zos.putNextEntry(new ZipEntry(domain + ".key"));
            zos.write(keyBytes);
            zos.closeEntry();
        }
        return new CertificateResult("/api/certificate/download/" + domain);
    }

    @Override
    public CertificateResult renewCertificate(String domain) throws Exception {
        KeyPair accountKey = (KeyPair) CACHE_MAP.get("accountKey:" + domain);
        KeyPair domainKey = (KeyPair) CACHE_MAP.get("domainKey:" + domain);
        Order order = (Order) CACHE_MAP.get("order:" + domain);
        if (accountKey == null || domainKey == null) {
            throw new IllegalStateException("账户或域名密钥不存在，无法续签");
        }

        // 使用已有账号会话
        Session session = getSession(getAcmeProvider(order));
        Account acct = new AccountBuilder().useKeyPair(accountKey).createLogin(session).getAccount();

        // 创建新订单（续签 = 新订单）
        order = acct.newOrder().domains(domain).create();

        for (Authorization auth : order.getAuthorizations()) {
            Dns01Challenge challenge = (Dns01Challenge) auth.findChallenge(Dns01Challenge.TYPE)
                    .orElseThrow(() -> new IllegalStateException("未找到 DNS-01 验证类型"));

            // 如果 challenge 已验证通过，跳过
            if (challenge.getStatus() == Status.VALID) {
                continue;
            }
            // 等待 DNS 生效
            DnsWaitUtil.waitForDnsPropagation(auth.getIdentifier().getDomain(), challenge.getDigest(),"1.1.1.1",60);

            // 触发验证
            challenge.trigger();

            int retries = 4;
            while (retries-- > 0) {
                challenge.update();
                if (challenge.getStatus() == Status.VALID) {
                    break;
                }
                TimeUnit.SECONDS.sleep(15);
            }

            if (challenge.getStatus() != Status.VALID) {
                throw new RuntimeException("DNS 验证失败，续签无法继续！");
            }
        }

        // 生成新 CSR 并提交
        CSRBuilder csrb = new CSRBuilder();
        csrb.addDomain(domain);
        csrb.sign(domainKey);
        order.execute(csrb.getEncoded());

        int wait = 2;
        while (wait-- > 0 && order.getStatus() != Status.VALID) {
            order.update();
            TimeUnit.SECONDS.sleep(15);
        }

        if (order.getStatus() != Status.VALID) {
            throw new RuntimeException("证书续签失败: 订单未完成");
        }

        Certificate cert = order.getCertificate();
        if (cert == null) {
            throw new RuntimeException("未能获取到续签证书");
        }

        // 保存证书
        Files.createDirectories(Paths.get("cert"));
        Path zipPath = Paths.get("cert", domain + ".zip");

        StringBuilder pemBuilder = new StringBuilder();
        for (X509Certificate c : cert.getCertificateChain()) {
            pemBuilder.append("-----BEGIN CERTIFICATE-----\n");
            pemBuilder.append(Base64.getMimeEncoder(64, new byte[]{'\n'}).encodeToString(c.getEncoded()));
            pemBuilder.append("\n-----END CERTIFICATE-----\n\n");
        }
        byte[] pemBytes = pemBuilder.toString().getBytes(StandardCharsets.UTF_8);

        StringWriter sw = new StringWriter();
        KeyPairUtils.writeKeyPair(domainKey, sw);
        byte[] keyBytes = sw.toString().getBytes(StandardCharsets.UTF_8);

        try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(zipPath,
                StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING))) {
            zos.putNextEntry(new ZipEntry(domain + ".pem"));
            zos.write(pemBytes);
            zos.closeEntry();

            zos.putNextEntry(new ZipEntry(domain + ".key"));
            zos.write(keyBytes);
            zos.closeEntry();
        }
        CACHE_MAP.remove("order:" + domain);
        CACHE_MAP.put("order:" + domain,order);
        return new CertificateResult("/api/certificate/download/" + domain);
    }

    private Session getSession(ACME acme){
        Session session;
        switch (acme){
            case LETS_ENCRYPT:
                session = new Session(!"dev".equals(env) ? ACME.LETS_ENCRYPT.getUrl() : "https://acme-staging-v02.api.letsencrypt.org/directory");
                break;
            case BUY_PASS_GO_SSL:
                session = new Session(ACME.BUY_PASS_GO_SSL.getUrl());
                break;
            case ZERO_SSL:
                session = new Session(ACME.ZERO_SSL.getUrl());
                break;
            default:
                session = new Session(ACME.GOOGLE_TRUST.getUrl());
        }
        return session;
    }

    private ACME getAcmeProvider(Order order) {
        if (order == null || order.getLocation() == null) {
            return ACME.LETS_ENCRYPT;
        }

        String host = order.getLocation().getHost();
        if (host == null) {
            return ACME.LETS_ENCRYPT;
        }

        host = host.toLowerCase();

        if (host.contains("letsencrypt")) {
            return ACME.LETS_ENCRYPT;
        } else if (host.contains("zerossl")) {
            return ACME.ZERO_SSL;
        } else if (host.contains("buypass")) {
            return ACME.BUY_PASS_GO_SSL;
        } else if (host.contains("googletrust")) {
            return ACME.GOOGLE_TRUST;
        } else {
            return ACME.LETS_ENCRYPT;
        }
    }
}
